﻿using NPOI.SS.Formula.Functions;
using NPOI.XWPF.UserModel;
using Sdk.FileReader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.Marshalling.IIUnknownCacheStrategy;

namespace Sdk.FileReader
{
    public class DocxReader : IFileReader
    {
        FileReadOptions opt = new FileReadOptions();

        static public string FieldName { get; } = "FieldName";

        static public string FieldType { get; } = "FieldType";

        static public string FieldMark { get; } = "FieldMark";

        static public string FieldComment { get; } = "FieldComment";


        public ReadDocResult ReadDoc(Stream stm, FileReadOptions options)
        {
            opt = options;
            List<TableInfo> tables = new List<TableInfo>();
            List<string> docExtCodes = new List<string>();

            using (XWPFDocument doc = new XWPFDocument(stm))
            {
                LineTypeEnum currentLineType = LineTypeEnum.Text;
                TableInfo? currentTable = null;
                TableTypeEnum currentTableType = TableTypeEnum.Other;
                string currentExtCode = "";

                foreach (var el in doc.BodyElements)
                {
                    if (el.ElementType == BodyElementType.PARAGRAPH)
                    {
                        var para = el as XWPFParagraph;
                        if (para == null)
                        {
                            continue;
                        }
                        var text = para.Text.Trim();
                        if (IsStructTableNameLine(text))
                        {
                            currentLineType = LineTypeEnum.TableName;
                            currentTableType = TableTypeEnum.Struct;
                            TableInfo tableInfo = new TableInfo();
                            currentTable = tableInfo;
                            tables.Add(tableInfo);
                            SetTableName(text, currentTable);
                        }
                        else if (IsDataTableNameLine(text))
                        {
                            currentLineType = LineTypeEnum.TableName;
                            currentTableType = TableTypeEnum.Data;
                            (string tableName, string tableComment) = GetTableName(text);
                            if (!string.IsNullOrEmpty(tableName))
                            {
                                var t = tables.FirstOrDefault(x => x.TableName.Equals(tableName, StringComparison.OrdinalIgnoreCase));
                                if (t != null)
                                {
                                    currentTable = t;
                                }
                            }
                        }
                        else if (IsSQLAreaBegin(text))
                        {
                            currentLineType = LineTypeEnum.SQL;
                            var tableName = GetSQLAreaTableName(text);
                            if (!string.IsNullOrEmpty(tableName))
                            {
                                var t = tables.FirstOrDefault(x => x.TableName.Equals(tableName, StringComparison.OrdinalIgnoreCase));
                                if (t != null)
                                {
                                    currentTable = t;
                                }
                            }
                        }
                        else if (currentLineType == LineTypeEnum.SQL)
                        {
                            if (IsSQLAreaEnd(text))
                            {
                                if (currentTable != null)
                                {
                                    currentTable.ExtCodes.Add(currentExtCode);
                                }
                                else
                                {
                                    docExtCodes.Add(currentExtCode);
                                }

                                currentLineType = LineTypeEnum.Text;
                                currentTableType = TableTypeEnum.Other;
                                currentTable = null;
                                currentExtCode = "";
                            }
                            else
                            {
                                currentExtCode += text;
                            }
                        }
                    }
                    else if (el.ElementType == BodyElementType.TABLE)
                    {
                        if (currentTable == null)
                        {
                            continue;
                        }
                        if (currentTableType == TableTypeEnum.Other)
                        {
                            continue;
                        }
                        var table = el as XWPFTable;
                        if (table == null)
                        {
                            continue;
                        }
                        //Console.WriteLine(tab.Text);
                        if (currentTableType == TableTypeEnum.Struct)
                        {
                            Dictionary<string, int>? headIndexs = null;
                            foreach (var row in table.Rows) 
                            {
                                if ((headIndexs == null) && IsStructTableHeadLine(row))
                                {
                                    currentLineType = LineTypeEnum.TableHead;
                                    headIndexs = GetStructTableHeadIndex(row);
                                }
                                else
                                {
                                    if (headIndexs == null)
                                    {
                                        break;
                                    }

                                    currentLineType = LineTypeEnum.TableRow;
                                    FieldInfo fieldInfo = new FieldInfo();
                                    fieldInfo.FieldName = row.GetCell(headIndexs[FieldName]).GetText().Trim();
                                    fieldInfo.FieldTypeDb = row.GetCell(headIndexs[FieldType]).GetText().Trim();
                                    fieldInfo.FieldMark = row.GetCell(headIndexs[FieldMark]).GetText().Trim();
                                    fieldInfo.FieldComment = row.GetCell(headIndexs[FieldComment]).GetText().Trim();
                                    if (!string.IsNullOrWhiteSpace(fieldInfo.FieldName))
                                    {
                                        currentTable.StructFields.Add(fieldInfo);
                                        fieldInfo.Parse();
                                    }
                                }
                            }
                        }
                        else if (currentTableType == TableTypeEnum.Data)
                        {
                            Dictionary<string, int>? headInfos = null;
                            for (int rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
                            { 
                                var row = table.Rows[rowIndex];
                                if (rowIndex == 0)
                                {
                                    currentLineType = LineTypeEnum.TableHead;
                                    headInfos = GetDataTableHeadIndex(row);
                                    foreach (var head in headInfos)
                                    {
                                        var field = currentTable.StructFields.FirstOrDefault(x => x.FieldName.ToLower() == head.Key.ToLower());
                                        if (field != null)
                                        {
                                            currentTable.DataFields.Add(field);
                                        }
                                    }
                                }
                                else 
                                {
                                    if (headInfos == null)
                                    {
                                        break;
                                    }

                                    currentLineType = LineTypeEnum.TableRow;
                                    RowData rowData = new RowData();
                                    currentTable.RowDatas.Add(rowData);

                                    rowData.RowIndex = rowIndex;
                                    foreach (var headInfo in headInfos)
                                    {
                                        var fieldName = headInfo.Key;
                                        var cellIndex = headInfo.Value;
                                        var fieldText = row.GetCell(cellIndex).GetText().Trim();
                                        var cellData = new CellData
                                        {
                                            CellIndex = cellIndex,
                                            FieldName = fieldName,
                                            CellText = fieldText
                                        };
                                        rowData.CellDatas.Add(cellData);
                                    }
                                }
                            }
                        }

                        currentLineType = LineTypeEnum.Text;
                        currentTableType = TableTypeEnum.Other;
                        currentTable = null;
                    }
                }
            }

            var result = new ReadDocResult 
            {
                Tables = tables,
                ExtCodes = docExtCodes
            };
            return result;
        }

        private bool IsStructTableNameLine(string text)
        {
            var c = opt.TableNameSplitChar.Trim();
            string[] keywords = [$"Table{c}", $"表名{c}"];
            foreach (var keyword in keywords)
            {
                if (text.StartsWith(keyword, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        private bool IsStructTableHeadLine(XWPFTableRow row)
        {
            string[] keywords = ["字段名", "数据类型", "字段类型", "标志", "字段标志", "备注", "字段备注",
                "FieldName", "FieldType", "FieldMark", "Remark"];
            var cells = row.GetTableCells();
            foreach (var cell in cells) 
            {
                var cellText = cell.GetText().Trim().ToLower();
                if (keywords.Contains(cellText))
                {
                    return true;
                }
            }
            return false;
        }

        private Dictionary<string, int> GetStructTableHeadIndex(XWPFTableRow row)
        {
            string[] keyFiledNames = ["字段名", "fieldname"];
            string[] keyFieldTypes = ["数据类型", "字段类型", "fieldtype"];
            string[] keyFieldMarks = ["标志", "字段标志", "fieldmark"];
            string[] keyComments = ["备注", "字段备注", "remark", "comment", "fieldcomment"];
            Dictionary<string, int> headIndexs = new Dictionary<string, int>();
            var cells = row.GetTableCells();
            for (int colIndex = 0; colIndex < cells.Count; colIndex++) 
            {
                var cellText = cells[colIndex].GetText().Trim().ToLower();
                if (keyFiledNames.Contains(cellText))
                {
                    headIndexs[FieldName] = colIndex;
                }
                else if (keyFieldTypes.Contains(cellText))
                {
                    headIndexs[FieldType] = colIndex;
                }
                else if (keyFieldMarks.Contains(cellText))
                {
                    headIndexs[FieldMark] = colIndex;
                }
                else if (keyComments.Contains(cellText))
                {
                    headIndexs[FieldComment] = colIndex;
                }
            }
            return headIndexs;
        }

        private Dictionary<string, int> GetDataTableHeadIndex(XWPFTableRow row)
        {
            Dictionary<string, int> headIndexs = new Dictionary<string, int>();
            var cells = row.GetTableCells();
            for (int colIndex = 0; colIndex < cells.Count; colIndex++)
            {
                var cellText = cells[colIndex].GetText().Trim();
                headIndexs[cellText] = colIndex;
            }
            return headIndexs;
        }

        private bool IsDataTableNameLine(string text)
        {
            var c = opt.TableNameSplitChar.Trim();
            string[] keywords = [$"Data{c}", $"数据{c}"];
            foreach (var keyword in keywords)
            {
                if (text.StartsWith(keyword, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        private (string, string) GetTableName(string text)
        {
            string tableName = "";
            string tableComment = "";

            var fields = text.Split(opt.TableNameSplitChar);
            var len = fields.Length;
            if (len > 1)
            {
                tableName = fields[1].Trim();
            }
            if (len > 2)
            {
                tableComment = fields[2].Trim();
            }
            return (tableName, tableComment);
        }

        private void SetTableName(string text, TableInfo tableInfo)
        {
            (string tableName, string tableComment) = GetTableName(text);
            tableInfo.TableName = tableName;
            tableInfo.Comment = tableComment;
        }

        private bool IsSQLAreaBegin(string text) 
        {
            string[] keywords = ["```SQL"];
            foreach (var keyword in keywords)
            {
                if (text.StartsWith(keyword, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        private bool IsSQLAreaEnd(string text)
        {
            string[] keywords = ["```"];
            foreach (var keyword in keywords)
            {
                if (text.StartsWith(keyword, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        private string GetSQLAreaTableName(string text)
        {
            string tableName = "";
            var fields = text.Split(opt.TableNameSplitChar);
            var len = fields.Length;
            if (len > 1)
            {
                tableName = fields[1].Trim();
            }
            return tableName;
        }
    }
}
